package com.gridsum.techpub.help.elasticsearch;


import com.gridsum.techpub.help.elasticsearch.annotation.ElasticProperty;
import com.gridsum.techpub.help.elasticsearch.enumtype.FieldType;
import com.gridsum.techpub.help.elasticsearch.enumtype.FieldIndexOption;
import com.gridsum.techpub.help.elasticsearch.enumtype.SubType;
import com.gridsum.techpub.help.elasticsearch.exception.AnnotationMissException;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * ElasticSearch Mapping Builder for ElasticSearch 2.X
 *
 */
public class MappingBuilder
{
    private static boolean defaultAnalysis = false;


    public static  <T>String builder(Class<T> clz) throws ClassNotFoundException {
       StringBuilder mappingBuilder = new StringBuilder("{");
       subObject(clz,mappingBuilder,false);
       return mappingBuilder.append("}").toString();
    }

    private static void subObject(Class<?> clz, StringBuilder mappingBuilder,boolean isNested) throws ClassNotFoundException {
        if (isNested)
            mappingBuilder.append("\"type\": \"nested\",");
        mappingBuilder.append("\"properties\": {");
        List<Field> fieldList = new ArrayList<>();
        Class current = clz;
        while (current != null){
            fieldList.addAll(Arrays.asList(current.getDeclaredFields()));
            current = current.getSuperclass();
        }
        Field[] fields = fieldList.toArray(new Field[]{});
        for (int i = 0; i < fields.length; i++) {
            mappingBuilder.append("\"").append(fields[i].getName()).append("\":{");
            String typeName = fields[i].getType().getName().replace("[L","").replace(";","");
            String type;
            if (typeName.equals("java.util.List")){
                Type genericType = fields[i].getGenericType();
                ParameterizedType parameterizedType = (ParameterizedType) genericType;
                Type realType = parameterizedType.getActualTypeArguments()[0];
                type = Mapping.getName(realType.getTypeName());
            }else{
                type = Mapping.getName(typeName);
            }
            ElasticProperty elasticProperty = fields[i].getDeclaredAnnotation(ElasticProperty.class);
            if (type==null){
                if (typeName.equals("java.util.List")){
                    Type genericType = fields[i].getGenericType();
                    ParameterizedType parameterizedType = (ParameterizedType) genericType;
                    Type realType = parameterizedType.getActualTypeArguments()[0];
                    Class<?> subClass = Class.forName(realType.getTypeName());
                    if (subClass.isEnum()){
                        mappingBuilder.append("\"type\": \"integer\"");
                    }else {
                        if(elasticProperty ==null || !FieldType.Nested.equals(elasticProperty.type()))
                            subObject(subClass,mappingBuilder,false);
                        else
                            subObject(subClass,mappingBuilder,true);
                    }
                }else{
                    if(elasticProperty ==null || !FieldType.Nested.equals(elasticProperty.type()))
                        subObject(Class.forName(typeName),mappingBuilder,false);
                    else
                        subObject(Class.forName(typeName),mappingBuilder,true);
                }
            }else {
                if (type.equals("string")){

                    if (elasticProperty==null){
                        if (defaultAnalysis){
                            throw new AnnotationMissException("java.lang.String field must have ElasticProperty annotation, check: " +clz.getName()+"." + fields[i].getName());
                        }else {
                            mappingBuilder.append("\"index\": \"not_analyzed\",");
                            mappingBuilder.append("\"include_in_all\": false,");
                        }

                    }else {
                        int ignoreAbove = elasticProperty.ignore_above();
                        SubType subtype = elasticProperty.subtype();
                        if (elasticProperty.index().equals(FieldIndexOption.NotAnalyzed)){
                            mappingBuilder.append("\"index\": \"not_analyzed\",");
                            mappingBuilder.append(Mapping.getSubType(subtype));
                            mappingBuilder.append("\"include_in_all\": "+elasticProperty.includeInAll()+",");
                            if (ignoreAbove>0){
                                mappingBuilder.append("\"ignore_above\": "+ignoreAbove+",");
                            }
                        }else if (elasticProperty.index().equals(FieldIndexOption.Analyzed)){
                            mappingBuilder.append("\"analyzer\": \""+elasticProperty.analyzer()+"\",");
                            mappingBuilder.append(Mapping.getSubType(subtype));
                            mappingBuilder.append("\"null_value\": \""+elasticProperty.nullValue()+"\",");
                        }
                    }
                }else if (type.equals("date")){
                    if (elasticProperty==null||"".equals(elasticProperty.format()))
                        mappingBuilder.append("\"format\": \"strict_date_optional_time||epoch_millis\",");
                    else
                        mappingBuilder.append("\"format\": \""+elasticProperty.format()+"\",");
                }
                mappingBuilder.append("\"type\": \"").append(type).append("\"");
            }
            mappingBuilder.append("}");
            if (fields.length -1 != i)
                mappingBuilder.append(",");
        }
        mappingBuilder.append("}");
    }

    public static void setDefaultAnalysis(boolean defaultAnalysis) {
        MappingBuilder.defaultAnalysis = defaultAnalysis;
    }
}
